//
//  KeyboardMachine.h
//  Clock Signal
//
//  Created by Thomas Harte on 05/11/2016.
//  Copyright 2016 Thomas Harte. All rights reserved.
//

#pragma once

#include "Inputs/Keyboard.hpp"

#include <bitset>
#include <cstdint>
#include <string>
#include <set>

namespace MachineTypes {

/*!
	Covers just the actions necessary to communicate keyboard state, as a purely abstract class.
*/
struct KeyActions {
	/*!
		Indicates that the key @c key has been either pressed or released, according to
		the state of @c isPressed.
	*/
	virtual void set_key_state([[maybe_unused]] uint16_t key, [[maybe_unused]] bool is_pressed) {}

	/*!
		Instructs that all keys should now be treated as released.
	*/
	virtual void clear_all_keys() {}

	/*!
		Indicates whether a machine most naturally accepts logical rather than physical input.
	*/
	virtual bool prefers_logical_input() { return false; }
};

/*!
	Describes an emulated machine which exposes a keyboard and accepts a typed string.
*/
struct KeyboardMachine: public KeyActions {
	/*!
		Causes the machine to attempt to type the supplied string.

		This is best effort. Success or failure is permitted to be a function of machine and current state.
	*/
	virtual void type_string(const std::string &);

	/*!
		@returns @c true if this machine can type the character @c c as part of a @c type_string; @c false otherwise.
	*/
	virtual bool can_type([[maybe_unused]] char c) const { return false; }

	/*!
		Provides a destination for keyboard input.
	*/
	virtual Inputs::Keyboard &get_keyboard() = 0;

	/*!
		Provides a standard bundle of logic for hosts that are able to correlate typed symbols
		with keypresses. Specifically:

		If map_logically is false:

			(i) initially try to set @c key as @c is_pressed;
			(ii) if this machine doesn't map @c key to anything but @c symbol is a printable ASCII character, attempt to @c type_string it.

		If map_logically is true:

			(i) if @c symbol can be typed and this is a key down, @c type_string it;
			(ii) if @c symbol cannot be typed, set @c key as @c is_pressed
	*/
	bool apply_key(
		const Inputs::Keyboard::Key key,
		const char symbol,
		const bool is_pressed,
		const bool is_repeat,
		const bool map_logically
	) {
		Inputs::Keyboard &keyboard = get_keyboard();

		if(!map_logically) {
			// Try a regular keypress first, and stop if that works.
			if(keyboard.set_key_pressed(key, symbol, is_pressed, is_repeat)) {
				return true;
			}

			// That having failed, if a symbol has been supplied then try typing it.
			if(is_pressed && symbol && can_type(symbol)) {
				char string[2] = { symbol, 0 };
				type_string(string);
				return true;
			}

			return false;
		} else {
			// Try to type first.
			if(is_pressed && symbol && can_type(symbol)) {
				char string[2] = { symbol, 0 };
				type_string(string);
				return true;
			}

			// That didn't work. Forward as a keypress. As, either:
			//	(i) this is a key down, but doesn't have a symbol, or is an untypeable symbol; or
			//	(ii) this is a key up, which it won't be an issue to miscommunicate.
			return keyboard.set_key_pressed(key, symbol, is_pressed, is_repeat);
		}
	}
};

/*!
	Provides a base class for machines that want to provide a keyboard mapper,
	allowing automatic mapping from keyboard inputs to KeyActions.
*/
class MappedKeyboardMachine: public Inputs::Keyboard::Delegate, public KeyboardMachine {
public:
	MappedKeyboardMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers = {});

	/*!
		A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
		See the character mapper for logical mapping.
	*/
	struct KeyboardMapper {
		virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key) const = 0;
	};

	/// Terminates a key sequence from the character mapper.
	static constexpr uint16_t KeyEndSequence = 0xffff;

	/*!
		Indicates that a key is not mapped (for the keyboard mapper) or that a
		character cannot be typed (for the character mapper).
	*/
	static constexpr uint16_t KeyNotMapped = 0xfffe;

	/*!
		Indicates that a key is not mapped (for the keyboard mapper) or that a
		character cannot be typed (for the character mapper).
	*/
	static constexpr uint16_t DelaySlot = 0xfffd;

	/*!
		Allows individual machines to provide the mapping between host keys
		as per Inputs::Keyboard and their native scheme.
	*/
	virtual KeyboardMapper *get_keyboard_mapper();

	/*!
		Provides a keyboard that obtains this machine's keyboard mapper, maps
		the key and supplies it via the KeyActions.
	*/
	virtual Inputs::Keyboard &get_keyboard() override;

private:
	bool keyboard_did_change_key(Inputs::Keyboard &, Inputs::Keyboard::Key, bool is_pressed) override;
	void reset_all_keys(Inputs::Keyboard &) override;
	Inputs::Keyboard keyboard_;
};

}
